/**
 * @(#)BeanClassInfo.java	0.1 Nov 6, 2007
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.wideskill.rpg4ejb3.type;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * @author: Luigi Scarpato <luigi.scarpato at wideskill.com>
 * 
 */
public class BeanClassInfo {
	private Class<?> _clazz;

	public BeanClassInfo(Class<?> clazz) {
		this._clazz = clazz;
	}

	public PropertyDescriptor[] getInterfacePropertyDescriptors() {
		List<PropertyDescriptor> pds = new ArrayList<PropertyDescriptor>();

		getInterfacePropertyDescriptors(_clazz, pds, new HashSet<Class<?>>());

		return (PropertyDescriptor[]) pds.toArray(new PropertyDescriptor[pds.size()]);
	}

	public void getInterfacePropertyDescriptors(Class<?> clazz, List<PropertyDescriptor> pds, Set<Class<?>> classes) {
		if (classes.contains(clazz))
			return;

		classes.add(clazz);

		try {
			Class<?>[] interfaces = getInterfaces();

			/**
			 * add base interface information
			 */
			BeanInfo info = Introspector.getBeanInfo(clazz);
			for (int j = 0; j < info.getPropertyDescriptors().length; j++) {
				PropertyDescriptor pd = info.getPropertyDescriptors()[j];
				if (!containsPropertyName(pds, pd.getName())) {
					pds.add(pd);
				}
			}

			/**
			 * add extended interface information
			 */
			for (int i = 0; i < interfaces.length; i++) {
				getInterfacePropertyDescriptors(interfaces[i], pds, classes);
			}
		} catch (java.beans.IntrospectionException e) {
		}
	}

	private boolean containsPropertyName(List<PropertyDescriptor> pds,
			String name) {
		for (Iterator<PropertyDescriptor> itr = pds.iterator(); itr.hasNext();) {
			PropertyDescriptor pd = itr.next();
			if (pd.getName().equals(name)) {
				return true;
			}
		}
		return false;
	}
	
	protected Annotation[] getAnnotations(final AnnotatedElement ao) {
		if (System.getSecurityManager() == null)
			return ao.getAnnotations();
		else {
			PrivilegedAction<Annotation[]> action = new PrivilegedAction<Annotation[]>() {
				public Annotation[] run() {
					return ao.getAnnotations();
				}
			};

			return AccessController.doPrivileged(action);
		}
	}
	
	protected boolean isAnnotationDeclaredLocally(final Class<? extends Annotation> annotationType, 
												  final AnnotatedElement ao) {

		boolean declaredLocally = false;
		
		for (final Annotation annotation : Arrays.asList(getAnnotations(ao))) {
			
			Class<? extends Annotation> at = annotation.annotationType();
			
			if ( (at.equals(annotationType)) || 
				 ((!at.getName().startsWith("java.lang.annotation.")) &&  isAnnotationDeclaredLocally(annotationType, at)))  {
				//(at == Documented.class) && 
				declaredLocally = true;
				break;
			}
		}
		return declaredLocally;
	}
	
	/**
	 * Find all fields of an object, whether private or public. We also look
	 * at the fields in super classes.
	 */
	protected <A extends Annotation>Field[] getDeclaredFieldsWithAnnotations(final Class<A> annotationType) {
		Collection<Field> result = new LinkedList<Field>();
		Class<?> c = _clazz;
		while (c != null) {
			Field[] f = getDeclaredFields(c);
			for (int i = 0; i < f.length; i++) {
				if (!Modifier.isStatic(f[i].getModifiers()) && 
						isAnnotationDeclaredLocally(annotationType, f[i])) {
					result.add(f[i]);
				}
			}
			c = c.getSuperclass();
		}
		
		return (Field[]) result.toArray(new Field[result.size()]); 
	}
	  
	private Field[] getDeclaredFields(final Class<?> clazz)
	{
		if (System.getSecurityManager() == null)
			return clazz.getDeclaredFields();
		else
		{
			PrivilegedAction<Field[]> action = new PrivilegedAction<Field[]>()
			{
				public Field[] run()
				{
					return clazz.getDeclaredFields();
				}
			};
			return AccessController.doPrivileged(action);
		}
	}

	protected Class<?>[] getInterfaces()
	{
		if (System.getSecurityManager() == null)
			return _clazz.getInterfaces();
		else
		{
			PrivilegedAction<Class<?>[]> action = new PrivilegedAction<Class<?>[]>()
			{
				public Class<?>[] run()
				{
					return _clazz.getInterfaces();
				}
			};
			return AccessController.doPrivileged(action);
		}
	}
	
    @SuppressWarnings("unchecked")
    static public <T> T toType(final Object object, final Class<T> type) {
        T result = null;
        
        if (object == null) {
            //initalize primitive types:
            if (type == Boolean.TYPE) {
                result = ((Class<T>) Boolean.class).cast(false);
            } else if (type == Byte.TYPE) {
                result = ((Class<T>) Byte.class).cast(0);
            } else if (type == Character.TYPE) {
                result = ((Class<T>) Character.class).cast(0);
            } else if (type == Double.TYPE) {
                result = ((Class<T>) Double.class).cast(0.0);
            } else if (type == Float.TYPE) {
                result = ((Class<T>) Float.class).cast(0.0);
            } else if (type == Integer.TYPE) {
                result = ((Class<T>) Integer.class).cast(0);
            } else if (type == Long.TYPE) {
                result = ((Class<T>) Long.class).cast(0);
            } else if (type == Short.TYPE) {
                result = ((Class<T>) Short.class).cast(0);
            }
        } else {
            final String so = ""+object;
            
            //custom type conversions:
            if (type == BigDecimal.class) {
               //result = type.cast(toBigDecimal(so, ICConstants.FORMAT_DECIMAL));
            } else if (type == BigInteger.class) {
                result = type.cast(new BigInteger(so));
            } else if (type == Boolean.class || type == Boolean.TYPE) {
                Boolean r = null;
                if ("1".equals(so) || "true".equalsIgnoreCase(so) || "yes".equalsIgnoreCase(so) || "on".equalsIgnoreCase(so)) {
                    r = Boolean.TRUE;
                } else if ("0".equals(object) || "false".equalsIgnoreCase(so) || "no".equalsIgnoreCase(so) || "off".equalsIgnoreCase(so)) {
                    r = Boolean.FALSE;
                } else {
                    r = Boolean.valueOf(so);
                }
                
                if (type == Boolean.TYPE) {
                    result = ((Class<T>) Boolean.class).cast(r); //avoid ClassCastException through autoboxing
                } else {
                    result = type.cast(r);
                }
            } else if (type == Byte.class || type == Byte.TYPE) {
                Byte i = Byte.valueOf(so);
                if (type == Byte.TYPE) {
                    result = ((Class<T>) Byte.class).cast(i); //avoid ClassCastException through autoboxing
                } else {
                    result = type.cast(i);
                }
            } else if (type == Character.class || type == Character.TYPE) {
                Character i = new Character(so.charAt(0));
                if (type == Character.TYPE) {
                    result = ((Class<T>) Character.class).cast(i); //avoid ClassCastException through autoboxing
                } else {
                    result = type.cast(i);
                }
            } else if (type == Double.class || type == Double.TYPE) {
                Double i = Double.valueOf(so);
                if (type == Double.TYPE) {
                    result = ((Class<T>) Double.class).cast(i); //avoid ClassCastException through autoboxing
                } else {
                    result = type.cast(i);
                }
            } else if (type == Float.class || type == Float.TYPE) {
                Float i = Float.valueOf(so);
                if (type == Float.TYPE) {
                    result = ((Class<T>) Float.class).cast(i); //avoid ClassCastException through autoboxing
                } else {
                    result = type.cast(i);
                }
            } else if (type == Integer.class || type == Integer.TYPE) {
                Integer i = Integer.valueOf(so);
                if (type == Integer.TYPE) {
                    result = ((Class<T>) Integer.class).cast(i); //avoid ClassCastException through autoboxing
                } else {
                    result = type.cast(i);
                }
            } else if (type == Long.class || type == Long.TYPE) {
                Long i = Long.valueOf(so);
                if (type == Long.TYPE) {
                    result = ((Class<T>) Long.class).cast(i); //avoid ClassCastException through autoboxing
                } else {
                    result = type.cast(i);
                }
            } else if (type == Short.class || type == Short.TYPE) {
                Short i = Short.valueOf(so);
                if (type == Short.TYPE) {
                    result = ((Class<T>) Short.class).cast(i); //avoid ClassCastException through autoboxing
                } else {
                    result = type.cast(i);
                }
            } else { //hard cast:
                result = type.cast(object);
            }
        }
        
        return result;
    }//toType()
}
